#include <defs.h>
#include <x86.h>
#include <elf.h>

/* *********************************************************************
 * This a dirt simple boot loader, whose sole job is to boot
 * an ELF kernel image from the first IDE hard disk.
 *
 * DISK LAYOUT
 *  * This program(bootasm.S and bootmain.c) is the bootloader.
 *    It should be stored in the first sector of the disk.
 *
 *  * The 2nd sector onward holds the kernel image.
 *
 *  * The kernel image must be in ELF format.
 *
 * BOOT UP STEPS
 *  * when the CPU boots it loads the BIOS into memory and executes it
 *
 *  * the BIOS intializes devices, sets of the interrupt routines, and
 *    reads the first sector of the boot device(e.g., hard-drive)
 *    into memory and jumps to it.
 *
 *  * Assuming this boot loader is stored in the first sector of the
 *    hard-drive, this code takes over...
 *
 *  * control starts in bootasm.S -- which sets up protected mode,
 *    and a stack so C code then run, then calls bootmain()
 *
 *  * bootmain() in this file takes over, reads in the kernel and jumps to it.
 * */

#define SECTSIZE        512
#define ELFHDR          ((struct elfhdr *)0x10000)      // scratch space

/* waitdisk - wait for disk ready */
//waitdisk() 函数用于等待磁盘准备就绪。它通过读取端口 0x1F7 来检查状态，
//只有在状态位符合条件（即标志位为 0x40）时，表示磁盘准备好了，函数才返回。
static void
waitdisk(void) {
    while ((inb(0x1F7) & 0xC0) != 0x40)
        /* do nothing */;
}

/* readsect - read a single sector at @secno into @dst */
//readsect() 函数的声明，接受两个参数：目标指针 dst 和扇区编号 secno。
static void
readsect(void *dst, uint32_t secno) {
    // wait for disk to be ready 调用 waitdisk() 函数，确保磁盘准备好进行读取。
    waitdisk();

    outb(0x1F2, 1);                         // count = 1 向端口 0x1F2 写入值 1，表示要读取的扇区数量为1。
    outb(0x1F3, secno & 0xFF);
    outb(0x1F4, (secno >> 8) & 0xFF);
    outb(0x1F5, (secno >> 16) & 0xFF);
    outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0);
    outb(0x1F7, 0x20);                      // cmd 0x20 - read sectors //写入命令 0x20 到端口 0x1F7，指示磁盘控制器执行读取扇区操作。
    //这几行将扇区号 secno 分成字节，并写入对应的端口，以指定要读取的扇区。端口 0x1F6 还设置了设备号。
    // wait for disk to be ready 再次调用 waitdisk()，以确保读取操作完成，磁盘再次准备好。
    waitdisk();

    // read a sector从端口 0x1F0 读取数据到目标地址 dst，每次读取 SECTSIZE / 4（即512字节/4 = 128次）。
    // insl 指令用于从端口读取数据到内存。
    insl(0x1F0, dst, SECTSIZE / 4);
}

/* *
 * readseg - read @count bytes at @offset from kernel into virtual address @va,
 * might copy more than asked.
 * */
//readseg() 函数定义，接受三个参数：虚拟地址 va、字节数 count 和内核中的偏移 offset。
static void
readseg(uintptr_t va, uint32_t count, uint32_t offset) {
    uintptr_t end_va = va + count;  //计算结束虚拟地址 end_va

    // round down to sector boundary 将虚拟地址 va 调整到扇区边界，确保从正确的地址读取数据。
    va -= offset % SECTSIZE;

    // translate from bytes to sectors; kernel starts at sector 1
    //将偏移量 offset 转换为扇区号 secno，因为内核镜像从第二个扇区（编号为1）开始存储。
    uint32_t secno = (offset / SECTSIZE) + 1;

    // If this is too slow, we could read lots of sectors at a time.
    // We'd write more to memory than asked, but it doesn't matter --
    // we load in increasing order.
    //循环读取数据，直到读取到结束虚拟地址 end_va。
    //每次调用 readsect() 读取一个扇区的数据到指定的虚拟地址。
    for (; va < end_va; va += SECTSIZE, secno ++) {
        readsect((void *)va, secno);
    }
}

/* bootmain - the entry of bootloader */
//bootmain() 函数是引导加载程序的入口点。
void
bootmain(void) {
    // read the 1st page off disk
    //从磁盘读取前8个扇区（4096字节）到 ELFHDR 指向的内存地址，准备解析ELF头。
    readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);

    // is this a valid ELF?
    //检查读取的ELF头部的魔数（magic number）是否有效。如果无效，跳转到 bad 标签处理。
    if (ELFHDR->e_magic != ELF_MAGIC) {
        goto bad;
    }
    //定义指针 ph 和 eph，分别指向程序头和程序头的结束位置。
    struct proghdr *ph, *eph;

    // load each program segment (ignores ph flags)
    //将程序头的起始地址设置为 ELFHDR 中的 e_phoff 偏移，并计算结束位置 eph。
    ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff);
    eph = ph + ELFHDR->e_phnum;
    //循环读取每个程序段的数据，调用 readseg() 将内核程序段加载到内存指定地址。
    for (; ph < eph; ph ++) {
        readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
    }

    // call the entry point from the ELF header
    // note: does not return
    //从ELF头部获取内核的入口点，调用该入口函数，注意此调用不会返回。
    ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();
//如果在之前的步骤中发现了错误，将会执行到 bad 标签处。
//这里发送了一些特定的端口命令，通常用于向调试端口或显示错误信息。
bad:
    outw(0x8A00, 0x8A00);
    outw(0x8A00, 0x8E00);

    /* do nothing */
    while (1);//进入无限循环，表示引导加载程序无法继续执行。这通常是为了指示错误状态，程序将不再继续。
}

